package com.fowo.api.common.mybatis.helper;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.TableFieldInfo;
import com.baomidou.mybatisplus.core.metadata.TableInfo;
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.fowo.api.common.model.RException;
import com.fowo.api.common.util.ListUtils;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.experimental.Accessors;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.session.SqlSession;
import org.springframework.beans.BeanUtils;
import org.springframework.jdbc.BadSqlGrammarException;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Consumer;
import java.util.stream.Collectors;

/**
 * 对象关联性 Help
 * @author yl_ls
 */
@Slf4j
@Component
public class ObjectAssociationHelper {
    @Resource
    private SqlSession sqlSession;

    private final Map<Class<?>, ObjectAssociationChecker> cacheClassCheckers = new ConcurrentHashMap<>();
    public ObjectAssociationChecker getAssociationChecker(Class<?> objectClass, Consumer<Builder> builderConsumer) {
        return cacheClassCheckers.computeIfAbsent(objectClass, oc -> {
            Builder builder = associationBuilder();
            builderConsumer.accept(builder);
            return builder.build();
        });
    }

    public Builder associationBuilder() {
        return new Builder(sqlSession);
    }

    public static class Builder {

        private final SqlSession sqlSession;
        private final List<JoinCheck> joinChecks = new ArrayList<>();

        public Builder(SqlSession sqlSession) {
            this.sqlSession = sqlSession;
        }

        public ObjectAssociationChecker build() {
            return new ObjectAssociationChecker(this);
        }

        public Builder addJoinCheck(Class<?> targetClass, String displayName, String fieldName, boolean listField) {
            JoinCheck joinCheck = new JoinCheck(targetClass, displayName, fieldName, listField);
            joinChecks.add(joinCheck);
            return this;
        }
    }

    @Getter
    @Setter
    @AllArgsConstructor
    @NoArgsConstructor
    public static class JoinCheck {
        private Class<?> targetClass;
        private String displayName;
        private String fieldName;
        private boolean listField;
    }

    @Getter
    @Setter
    public static class RuntimeJoinCheck extends JoinCheck {
        private TableFieldInfo tableFieldInfo;
    }

    public static class ObjectAssociationChecker {
        private final Builder builder;
        private final Map<Class<?>, List<RuntimeJoinCheck>> targetJoinChecks;

        public ObjectAssociationChecker(Builder builder) {
            this.builder = builder;
            // 将目标对象分组
            targetJoinChecks = new HashMap<>();
            builder.joinChecks.forEach(joinCheck -> targetJoinChecks.compute(joinCheck.targetClass, (k, list) -> {
                if (list == null) {
                    list = new ArrayList<>();
                }
                RuntimeJoinCheck runtimeJoinCheck = new RuntimeJoinCheck();
                BeanUtils.copyProperties(joinCheck, runtimeJoinCheck);
                TableInfo tableInfo = TableInfoHelper.getTableInfo(joinCheck.targetClass);
                if (tableInfo == null) {
                    throw new RException("未能找到" + joinCheck.targetClass.getName() + "的表信息");
                }
                TableFieldInfo tableFieldInfo = ListUtils.find(tableInfo.getFieldList(), f -> f.getField().getName().equals(runtimeJoinCheck.getFieldName()));
                runtimeJoinCheck.setTableFieldInfo(tableFieldInfo);
                list.add(runtimeJoinCheck);
                return list;
            }));
        }

        /**
         * 快速检查，有一个关联结果就返回
         * @param idList 要检查的编号列表
         * @return 检查结果
         */
        public CheckResults checkAny(List<Long> idList) {
            // 清理空主键
            List<Long> clearIdList = idList.stream().filter(Objects::nonNull).collect(Collectors.toList());
            CheckResults results = new CheckResults();
            if (clearIdList.isEmpty()) {
                return results;
            }
            for (Map.Entry<Class<?>, List<RuntimeJoinCheck>> targetJoinCheck : targetJoinChecks.entrySet()) {
                try {
                    checkTargetJoinChecks(results, targetJoinCheck.getKey(), targetJoinCheck.getValue(), idList);
                    if (results.exists()) {
                        break;
                    }
                } catch (BadSqlGrammarException e) {
                    log.warn("已忽略 SQL 语法错误 ", e);
                }
            }
            return results;
        }

        /**
         * 全量检查，返回全部的检查结果
         * @param idList 要检查的编号列表
         * @return 检查结果
         */
        public CheckResults check(List<Long> idList) {
            // 清理空主键
            List<Long> clearIdList = idList.stream().filter(Objects::nonNull).collect(Collectors.toList());
            CheckResults results = new CheckResults();
            if (clearIdList.isEmpty()) {
                return results;
            }
            for (Map.Entry<Class<?>, List<RuntimeJoinCheck>> targetJoinCheck : targetJoinChecks.entrySet()) {
                try {
                    checkTargetJoinChecks(results, targetJoinCheck.getKey(), targetJoinCheck.getValue(), idList);
                } catch (BadSqlGrammarException e) {
                    log.warn("已忽略 SQL 语法错误 ", e);
                }
            }
            return results;
        }

        @SuppressWarnings({"rawtypes", "unchecked"})
        private void checkTargetJoinChecks(CheckResults results, Class<?> targetClass, List<RuntimeJoinCheck> joinChecks, List<Long> idList) {
            JoinCheck joinCheck = joinChecks.get(0);
            String mapperClassName = "com.fowo.api.mapper." + targetClass.getSimpleName() + "Mapper";
            Class<?> mapperClass;
            try {
                mapperClass = Class.forName(mapperClassName);
            } catch (ClassNotFoundException e) {
                throw new RuntimeException(e);
            }
            BaseMapper mapper = (BaseMapper) builder.sqlSession.getMapper(mapperClass);
            QueryWrapper<Object> query = Wrappers.query().eq("del_flag", false);
            query.select("id");
            for (RuntimeJoinCheck check : joinChecks) {
                if (check.isListField()) {
                    // 处理列表类型
                    query.or(group -> idList.forEach(id -> group.like(check.tableFieldInfo.getColumn(), String.format("%%%s%%", id))));
                } else {
                    // 处理单值类型
                    query.in(check.tableFieldInfo.getColumn(), idList);
                }
            }
            // 先做一次记数查询，优化运行速度
            boolean exists = mapper.exists(query);
            if (exists) {
                List<Map> list = mapper.selectMaps(query);
                for (Map row : list) {
                    results.results.add(new CheckResult()
                            .setObjectName(joinCheck.displayName)
                            .setId((Long) row.get("id"))
                    );
                }
            }
        }
    }

    @Getter
    @Setter
    @Accessors(chain = true)
    public static class CheckResult {
        private String objectName;
        private Long id;
        private String title;
    }

    @Getter
    @Setter
    public static class CheckResults {
        private List<CheckResult> results = new ArrayList<>();
        public boolean exists() {
            return !results.isEmpty();
        }
        public boolean isEmpty() {
            return results.isEmpty();
        }

        /**
         * 整合一个简单的消息，（最多显示 9 个结果行 + 总结果数）
         * @return 简单的消息
         */
        public String packShortMessage() {
            if (results.isEmpty()) {
                return "";
            }
            StringBuilder sb = new StringBuilder();
            for(int i = 0; i < 9 && i < results.size(); i++) {
                CheckResult checkResult = results.get(i);
                sb.append("在").append(checkResult.objectName).append("中被记录");
                if (StringUtils.hasText(checkResult.title)) {
                    sb.append(checkResult.title);
                }
                sb.append("(ID:").append(checkResult.id).append(")引用");
                sb.append("\n");
            }
            if (results.size() > 9) {
                sb.append("还有").append(results.size() - 9).append("项引用已忽略");
            }
            return sb.toString();
        }
    }
}
